import { getInlineCode } from '../utils';
import {
  ScriptObject,
  StyleObject,
  TemplateResult
} from '../types/template.type';

const ALL_SCRIPT_REGEX = /(<script[\s\S]*?>)[\s\S]*?<\/script>/gi;
const SCRIPT_TAG_REGEX =
  /<(script)\s+((?!type=('|")text\/ng-template\3).)*?>.*?<\/\1>/is;
const SCRIPT_SRC_REGEX = /.*\ssrc=('|")?([^>'"\s]+)/;
const SCRIPT_TYPE_REGEX = /.*\stype=('|")?([^>'"\s]+)/;
const SCRIPT_ENTRY_REGEX = /.*\sentry\s*.*/;
const SCRIPT_ASYNC_REGEX = /.*\sasync\s*.*/;
const DEFER_ASYNC_REGEX = /.*\sdefer\s*.*/;
const SCRIPT_NO_MODULE_REGEX = /.*\snomodule\s*.*/;
const SCRIPT_MODULE_REGEX = /.*\stype=('|")?module('|")?\s*.*/;
const LINK_TAG_REGEX = /<(link)\s+.*?>/gis;
const LINK_PRELOAD_OR_PREFETCH_REGEX =
  /\srel=('|")?(preload|prefetch|modulepreload)\1/;
const LINK_HREF_REGEX = /.*\shref=('|")?([^>'"\s]+)/;
const LINK_AS_FONT = /.*\sas=('|")?font\1.*/;
const STYLE_TAG_REGEX = /<style[^>]*>[\s\S]*?<\/style>/gi;
const STYLE_TYPE_REGEX = /\s+rel=('|")?stylesheet\1.*/;
const STYLE_HREF_REGEX = /.*\shref=('|")?([^>'"\s]+)/;
const HTML_COMMENT_REGEX = /<!--([\s\S]*?)-->/g;
const LINK_IGNORE_REGEX = /<link(\s+|\s+.+\s+)ignore(\s*|\s+.*|=.*)>/is;
const STYLE_IGNORE_REGEX = /<style(\s+|\s+.+\s+)ignore(\s*|\s+.*|=.*)>/is;
const SCRIPT_IGNORE_REGEX = /<script(\s+|\s+.+\s+)ignore(\s*|\s+.*|=.*)>/is;
const CROSS_ORIGIN_REGEX =
  /.*\scrossorigin=?('|")?(use-credentials|anonymous)?('|")?/i;

/**
 * @Author: chen.long
 * @Descripttion: 处理http协议
 * @Param:
 * @param {*} url
 */
function hasProtocol(url) {
  return (
    url.startsWith('//') ||
    url.startsWith('http://') ||
    url.startsWith('https://')
  );
}

/**
 * @Author: chen.long
 * @Descripttion: 获取地址
 * @Param:
 * @param {*} path 相对地址
 * @param {*} baseURI 主地址
 */
function getEntirePath(path, baseURI) {
  return new URL(path, baseURI).toString();
}

/**
 * @Author: chen.long
 * @Descripttion: 判断脚本标签是否是合法的
 * @Param:
 * @param {*} type 传入的脚本类型
 */
function isValidJavaScriptType(type) {
  const handleTypes = [
    'text/javascript',
    'module',
    'application/javascript',
    'text/ecmascript',
    'application/ecmascript'
  ];
  return !type || handleTypes.indexOf(type) !== -1;
}

/**
 * @Author: chen.long
 * @Descripttion: 解析标签的属性
 * @Param:
 * @param {*} TagOuterHTML script 标签的 outerHTML
 *  * @returns 返回一个对象，包含 script 标签的所有属性
 */
export function parseTagAttributes(TagOuterHTML) {
  const pattern = /<[-\w]+\s+([^>]*)>/i;
  const matches = pattern.exec(TagOuterHTML);

  if (!matches) {
    return {};
  }

  const attributesString = matches[1];
  const attributesPattern = /([^\s=]+)\s*=\s*(['"])(.*?)\2/g;
  const attributesObject = {};

  let attributeMatches;
  while (
    (attributeMatches = attributesPattern.exec(attributesString)) !== null
  ) {
    const attributeName = attributeMatches[1];
    const attributeValue = attributeMatches[3];
    attributesObject[attributeName] = attributeValue;
  }

  return attributesObject;
}

/**
 * @Author: chen.long
 * @Descripttion: 判断是否支持模块导入
 * @Param:
 */
function isModuleScriptSupported() {
  const s = window.document.createElement('script');
  return 'noModule' in s;
}

/**
 * @Author: chen.long
 * @Descripttion: 样式注释
 */
export const genLinkReplaceSymbol = (linkHref, preloadOrPrefetch = false) =>
  `<!-- ${
    preloadOrPrefetch ? 'prefetch/preload/modulepreload' : ''
  } link ${linkHref} replaced by ybmicro -->`;
/**
 * @Author: chen.long
 * @Descripttion: 内联样式注释
 */
export const getInlineStyleReplaceSymbol = index =>
  `<!-- inline-style-${index} replaced by ybmicro -->`;
/**
 * @Author: chen.long
 * @Descripttion: 脚本注释
 */
export const genScriptReplaceSymbol = (scriptSrc, type = '') =>
  `<!-- ${type} script ${scriptSrc} replaced by ybmicro -->`;
/**
 * @Author: chen.long
 * @Descripttion: 内联脚本注释
 */
export const inlineScriptReplaceSymbol =
  '<!-- inline scripts replaced by ybmicro -->';
/**
 * @Author: chen.long
 * @Descripttion: 忽略资源注释
 */
export const genIgnoreAssetReplaceSymbol = url =>
  `<!-- ignore asset ${url || 'file'} replaced by ybmicro -->`;
/**
 * @Author: chen.long
 * @Descripttion: 忽略脚本注释
 */
export const genModuleScriptReplaceSymbol = (scriptSrc, moduleSupport) =>
  `<!-- ${
    moduleSupport ? 'nomodule' : 'module'
  } script ${scriptSrc} ignored by ybmicro -->`;

/**
 * @Author: chen.long
 * @Descripttion: 将样式和脚本链接从模版文件中解析出来，收集样式，使用全局的eval去执行内联脚本
 */
export default function processTpl(
  tpl: String,
  baseURI: String,
  postProcessTemplate?: Function
): TemplateResult {
  const scripts: ScriptObject[] = [];
  const styles: StyleObject[] = [];
  let entry = null;
  const moduleSupport = isModuleScriptSupported();
  const template = tpl

    // 移除html注释
    .replace(HTML_COMMENT_REGEX, '')

    .replace(LINK_TAG_REGEX, match => {
      // 修改css link
      const styleType = !!match.match(STYLE_TYPE_REGEX);
      if (styleType) {
        const styleHref = match.match(STYLE_HREF_REGEX);
        const styleIgnore = match.match(LINK_IGNORE_REGEX);

        if (styleHref) {
          const href = styleHref && styleHref[2];
          let newHref = href;

          if (href && !hasProtocol(href)) {
            newHref = getEntirePath(href, baseURI);
          }
          if (styleIgnore) {
            return genIgnoreAssetReplaceSymbol(newHref);
          }

          styles.push({ src: newHref });
          return genLinkReplaceSymbol(newHref);
        }
      }

      const preloadOrPrefetchType =
        match.match(LINK_PRELOAD_OR_PREFETCH_REGEX) &&
        match.match(LINK_HREF_REGEX) &&
        !match.match(LINK_AS_FONT);
      if (preloadOrPrefetchType) {
        const [, , linkHref] = match.match(LINK_HREF_REGEX);
        return genLinkReplaceSymbol(linkHref, true);
      }

      return match;
    })
    .replace(STYLE_TAG_REGEX, match => {
      if (STYLE_IGNORE_REGEX.test(match)) {
        return genIgnoreAssetReplaceSymbol('style file');
      } else {
        const code = getInlineCode(match);
        styles.push({ src: '', content: code });
        return getInlineStyleReplaceSymbol(styles.length - 1);
      }
    })
    .replace(ALL_SCRIPT_REGEX, (match, scriptTag) => {
      const scriptIgnore = scriptTag.match(SCRIPT_IGNORE_REGEX);
      const isModuleScript = !!scriptTag.match(SCRIPT_MODULE_REGEX);
      const isCrossOriginScript = scriptTag.match(CROSS_ORIGIN_REGEX);
      const crossOriginType = isCrossOriginScript?.[2] || '';
      const moduleScriptIgnore =
        (moduleSupport && !!scriptTag.match(SCRIPT_NO_MODULE_REGEX)) ||
        (!moduleSupport && isModuleScript);
      // 确保JavaScript的执行顺序
      const matchedScriptTypeMatch = scriptTag.match(SCRIPT_TYPE_REGEX);
      const matchedScriptType =
        matchedScriptTypeMatch && matchedScriptTypeMatch[2];
      if (!isValidJavaScriptType(matchedScriptType)) {
        return match;
      }

      // 处理外挂脚本
      if (SCRIPT_TAG_REGEX.test(match) && scriptTag.match(SCRIPT_SRC_REGEX)) {
        // 收集脚本并做替换
        const matchedScriptEntry = scriptTag.match(SCRIPT_ENTRY_REGEX);
        const matchedScriptSrcMatch = scriptTag.match(SCRIPT_SRC_REGEX);
        let matchedScriptSrc =
          matchedScriptSrcMatch && matchedScriptSrcMatch[2];

        if (entry && matchedScriptEntry) {
          throw new SyntaxError('You should not set multiply entry script!');
        } else {
          // 处理脚本协议前缀
          if (matchedScriptSrc && !hasProtocol(matchedScriptSrc)) {
            matchedScriptSrc = getEntirePath(matchedScriptSrc, baseURI);
          }

          entry = entry || (matchedScriptEntry && matchedScriptSrc);
        }

        if (scriptIgnore) {
          return genIgnoreAssetReplaceSymbol(matchedScriptSrc || 'js file');
        }

        if (moduleScriptIgnore) {
          return genModuleScriptReplaceSymbol(
            matchedScriptSrc || 'js file',
            moduleSupport
          );
        }

        if (matchedScriptSrc) {
          const isAsyncScript = !!scriptTag.match(SCRIPT_ASYNC_REGEX);
          const isDeferScript = !!scriptTag.match(DEFER_ASYNC_REGEX);
          scripts.push(
            isAsyncScript || isDeferScript
              ? {
                  async: isAsyncScript,
                  defer: isDeferScript,
                  src: matchedScriptSrc,
                  module: isModuleScript,
                  crossorigin: !!isCrossOriginScript,
                  crossoriginType: crossOriginType,
                  attrs: parseTagAttributes(match)
                }
              : {
                  src: matchedScriptSrc,
                  module: isModuleScript,
                  crossorigin: !!isCrossOriginScript,
                  crossoriginType: crossOriginType,
                  attrs: parseTagAttributes(match)
                }
          );
          return genScriptReplaceSymbol(
            matchedScriptSrc,
            (isAsyncScript && 'async') || (isDeferScript && 'defer') || ''
          );
        }

        return match;
      } else {
        if (scriptIgnore) {
          return genIgnoreAssetReplaceSymbol('js file');
        }

        if (moduleScriptIgnore) {
          return genModuleScriptReplaceSymbol('js file', moduleSupport);
        }

        // 判断内联脚本
        const code = getInlineCode(match);

        // 处理注释情况，将注释的代码行移除掉
        const isPureCommentBlock = code
          .split(/[\r\n]+/)
          .every(line => !line.trim() || line.trim().startsWith('//'));

        if (!isPureCommentBlock && code) {
          scripts.push({
            src: '',
            content: code,
            module: isModuleScript,
            crossorigin: !!isCrossOriginScript,
            crossoriginType: crossOriginType,
            attrs: parseTagAttributes(match)
          });
        }

        return inlineScriptReplaceSymbol;
      }
    });

  let tplResult = {
    template,
    scripts,
    styles,
    entry: entry || scripts[scripts.length - 1]
  };
  if (typeof postProcessTemplate === 'function') {
    tplResult = postProcessTemplate(tplResult);
  }

  return tplResult;
}
